/**
 *
 *    Copyright (c) 2023 Project CHIP Authors
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

#pragma once

#include <app-common/zap-generated/cluster-enums.h>
#include <app/icd/server/ICDServerConfig.h>
#include <lib/core/Optional.h>
#include <lib/support/BitFlags.h>
#include <lib/support/TimeUtils.h>
#include <optional>
#include <platform/CHIPDeviceConfig.h>
#include <protocols/secure_channel/CheckInCounter.h>
#include <system/SystemClock.h>

namespace chip {

namespace app {
// Forward declaration of ICDManager to allow it to be friend with ICDConfigurationData.
class ICDManager;
} // namespace app

namespace Testing {
// Forward declaration of ICDConfigurationDataTestAccess tests to allow it to be friend with the ICDConfigurationData.
// Used in unit tests
class ICDConfigurationDataTestAccess;
} // namespace Testing

/**
 * @brief ICDConfigurationData manages and stores ICD related configurations for the ICDManager.
 *        Goal of the class is to expose ICD information to all consummers without creating circular dependencies
 *        since the ICDManager is appart of the App layer
 *
 *        Anyone can read the ICD configurations but only the ICDManager can changes those configurations.
 */
class ICDConfigurationData
{
public:
    static constexpr uint32_t kICDCounterPersistenceIncrement = 100;

    enum class ICDMode : uint8_t
    {
        SIT, // Short Interval Time ICD
        LIT, // Long Interval Time ICD
    };

    static ICDConfigurationData & GetInstance() { return instance; };

    // This represents the ICDManagement Cluster's fixed value mIdleModeDuration Attribute
    System::Clock::Seconds32 GetIdleModeDuration() { return mIdleModeDuration; }

    System::Clock::Milliseconds32 GetActiveModeDuration() { return mActiveModeDuration; }

    System::Clock::Milliseconds16 GetActiveModeThreshold() { return mActiveThreshold; }

    System::Clock::Milliseconds32 GetGuaranteedStayActiveDuration() { return kGuaranteedStayActiveDuration; }

    Protocols::SecureChannel::CheckInCounter & GetICDCounter() { return mICDCounter; }

    uint16_t GetClientsSupportedPerFabric() { return mFabricClientsSupported; }

    System::Clock::Milliseconds32 GetSITPollingThreshold() { return kSITPollingThreshold; }

    System::Clock::Milliseconds32 GetFastPollingInterval() { return mFastPollingInterval; }

    System::Clock::Milliseconds16 GetMinLitActiveModeThreshold() { return kMinLitActiveModeThreshold; }

    System::Clock::Seconds32 GetMaximumCheckInBackoff() { return mMaximumCheckInBackOff; }

    BitFlags<app::Clusters::IcdManagement::Feature> GetFeatureMap() { return mFeatureMap; }

    /**
     * The returned value will depend on the devices operating mode.
     * If ICDMode == SIT && the configured slow poll interval is superior to the maximum threshold (15s), the function will return
     * the threshold kSITPollingThreshold (<= 15s). If ICDMode == SIT but the configured slow poll interval is equal or inferior to
     * the threshold, the function will the return the configured slow poll interval. If ICDMode == LIT, the function will return
     * the configured slow poll interval.
     *
     * @return System::Clock::Milliseconds32
     */
    System::Clock::Milliseconds32 GetSlowPollingInterval();

    /**
     * @brief Indicates if the device should apply the ShortIdleModeDuration instead of the normal IdleModeDuration.
     *
     * Uses ShortIdle only when:
     *  - ShortIdleModeDuration < IdleModeDuration
     *  - Long Idle Time feature is supported
     *  - Device currently operates in SIT mode
     *
     * NOTE: To make full use of the ShortIdleModeDuration, users SHOULD also set ICD_REPORT_ON_ENTER_ACTIVE_MODE
     *
     * @return true if ShortIdleModeDuration shall be used, false otherwise.
     */
    bool ShouldUseShortIdle();

    /**
     * @brief Returns the appropriate Idle duration based on current operating conditions.
     *
     * If ShouldUseShortIdle() is true, returns ShortIdleModeDuration; otherwise returns IdleModeDuration.
     *
     * @return Effective IdleMode duration in seconds.
     */
    System::Clock::Seconds32 GetModeBasedIdleModeDuration();

    ICDMode GetICDMode() { return mICDMode; }

private:
    // Singleton Object
    ICDConfigurationData()
    {
        // Initialize feature map
#if CHIP_CONFIG_ENABLE_ICD_CIP
        mFeatureMap.Set(app::Clusters::IcdManagement::Feature::kCheckInProtocolSupport);
#endif // CHIP_CONFIG_ENABLE_ICD_CIP
#if CHIP_CONFIG_ENABLE_ICD_UAT
        mFeatureMap.Set(app::Clusters::IcdManagement::Feature::kUserActiveModeTrigger);
#endif // CHIP_CONFIG_ENABLE_ICD_UAT
#if CHIP_CONFIG_ENABLE_ICD_LIT
        mFeatureMap.Set(app::Clusters::IcdManagement::Feature::kLongIdleTimeSupport);
#if CHIP_CONFIG_ENABLE_ICD_DSLS
        mFeatureMap.Set(app::Clusters::IcdManagement::Feature::kDynamicSitLitSupport);
#endif // CHIP_CONFIG_ENABLE_ICD_DSLS
#endif // CHIP_CONFIG_ENABLE_ICD_LIT
    }
    static ICDConfigurationData instance;

    // ICD related information is managed by the ICDManager but stored in the ICDConfigurationData to enable consummers to access it
    // without creating a circular dependency. To avoid allowing consummers changing the state of the ICD mode without going through
    // the ICDManager, the ICDManager is a friend that can access the private setters. If a consummer needs to be notified when a
    // value is changed, they can leverage the Observer events the ICDManager generates. See src/app/icd/server/ICDStateObserver.h
    friend class chip::app::ICDManager;

    friend class chip::Testing::ICDConfigurationDataTestAccess;

    void SetICDMode(ICDMode mode) { mICDMode = mode; };
    void SetFastPollingInterval(System::Clock::Milliseconds32 fastPollInterval) { mFastPollingInterval = fastPollInterval; };

    /**
     * @brief Sets the slow polling interval for the ICD.
     *
     * If LIT support is not enabled, the interval cannot be set higher than the SIT polling threshold.
     * If LIT support is enabled, any value is accepted.
     *
     * @param[in] slowPollInterval The slow polling interval in milliseconds.
     * @return CHIP_ERROR CHIP_NO_ERROR on success, CHIP_ERROR_INVALID_ARGUMENT if the value is invalid.
     */
    CHIP_ERROR SetSlowPollingInterval(System::Clock::Milliseconds32 slowPollInterval);

    /**
     * @brief Sets the SIT Idle polling interval.
     *
     * This function sets the slow/idle polling interval, which is used when the configured
     * slow polling interval exceeds the allowed threshold for SIT mode. The provided value must
     * be less than, or equal to the SIT polling threshold (kSITPollingThreshold).
     *
     * This SIT Slow Polling configuration allows ICD LIT device to configure a longer SlowPollingInterval
     * when operating as LIT, but use a faster SlowPollingInterval when the device must operate in SIT mode
     *
     * @param[in] pollingInterval The SIT slow polling interval in milliseconds.
     * @return CHIP_ERROR CHIP_NO_ERROR on success, CHIP_ERROR_INVALID_ARGUMENT if the value is invalid.
     */
    CHIP_ERROR SetSITPollingInterval(System::Clock::Milliseconds32 pollingInterval);

    static constexpr System::Clock::Milliseconds16 kMinLitActiveModeThreshold = System::Clock::Milliseconds16(5000);

    /**
     * @brief Change the ActiveModeDuration or the IdleModeDuration value
     *        If only one value is provided, check will be done agaisn't the other already set value.
     *
     * @param[in] activeModeDuration new ActiveModeDuration value
     * @param[in] idleModeDuration new IdleModeDuration value
     *                                The precision of the IdleModeDuration must be seconds.
     * Note: mIdleModeDuration is expressed in seconds. The idleModeDuration parameter is in milliseconds for backward compatibility
     * reasons. The conversion to seconds is done inside the function.
     *
     * @return CHIP_ERROR CHIP_ERROR_INVALID_ARGUMENT is returned if idleModeDuration_ms is smaller than activeModeDuration_ms
     *                                                is returned if idleModeDuration_ms is greater than 64800000 ms
     *                                                is returned if idleModeDuration_ms is smaller than 1000 ms
     *                                                is returned if no valid values are provided
     *                    CHIP_NO_ERROR is returned if the new intervals were set
     */
    CHIP_ERROR SetModeDurations(Optional<System::Clock::Milliseconds32> activeModeDuration,
                                Optional<System::Clock::Milliseconds32> idleModeDuration);

    /**
     * @brief Change the ActiveModeDuration, IdleModeDuration and/or ShortIdleModeDuration values.
     *
     * At least one of the three parameters must be provided. Omitted parameters keep their current stored values.
     *
     * Constraints:
     *  - activeModeDuration (ms) must be <= resulting idleModeDuration (s)
     *  - idleModeDuration (s) must be within [kMinIdleModeDuration, kMaxIdleModeDuration]
     *  - shortIdleModeDuration (s) must be <= resulting idleModeDuration (s)
     *
     * NOTE: to keep previous behavior, If shortIdleModeDuration is not provided, mShortIdleModeDuration can be clamped to resulting
     * idleModeDuration if the later becomes lesser than the previous mShortIdleModeDuration.
     *
     * @param[in] activeModeDuration      New ActiveModeDuration in milliseconds (optional).
     * @param[in] idleModeDuration        New IdleModeDuration in seconds (optional).
     * @param[in] shortIdleModeDuration   New ShortIdleModeDuration in seconds (optional, must not exceed IdleModeDuration).
     *
     * @return CHIP_NO_ERROR on success.
     * @return CHIP_ERROR_INVALID_ARGUMENT when:
     *         - no parameter is provided
     *         - activeModeDuration > resulting idleModeDuration
     *         - idleModeDuration is greater than 64800 s or is smaller than 1 s
     *         - shortIdleModeDuration > resulting idleModeDuration
     */
    CHIP_ERROR SetModeDurations(std::optional<System::Clock::Milliseconds32> activeModeDuration,
                                std::optional<System::Clock::Seconds32> idleModeDuration,
                                std::optional<System::Clock::Seconds32> shortIdleModeDuration);

    void SetFeatureMap(BitFlags<app::Clusters::IcdManagement::Feature> featureMap) { mFeatureMap = featureMap; }

    static constexpr System::Clock::Seconds32 kMaxIdleModeDuration = System::Clock::Seconds32(18 * kSecondsPerHour);
    static constexpr System::Clock::Seconds32 kMinIdleModeDuration = System::Clock::Seconds32(1);
    // As defined in the spec, the maximum guaranteed duration for the StayActiveDuration is 30s  "Matter Application
    // Clusters: 9.17.7.5.1. PromisedActiveDuration Field"
    static constexpr System::Clock::Milliseconds32 kGuaranteedStayActiveDuration = System::Clock::Milliseconds32(30000);

    static_assert((CHIP_CONFIG_ICD_IDLE_MODE_DURATION_SEC) <= kMaxIdleModeDuration.count(),
                  "Spec requires the IdleModeDuration to be equal or inferior to 64800s.");
    static_assert((CHIP_CONFIG_ICD_IDLE_MODE_DURATION_SEC) >= kMinIdleModeDuration.count(),
                  "Spec requires the IdleModeDuration to be equal or greater to 1s.");
    System::Clock::Seconds32 mIdleModeDuration = System::Clock::Seconds32(CHIP_CONFIG_ICD_IDLE_MODE_DURATION_SEC);

    // Shorter idleModeDuration when a LIT capable device operates in SIT mode.
    System::Clock::Seconds32 mShortIdleModeDuration = System::Clock::Seconds32(CHIP_CONFIG_ICD_SHORT_IDLE_MODE_DURATION_SEC);
    static_assert((CHIP_CONFIG_ICD_SHORT_IDLE_MODE_DURATION_SEC <= CHIP_CONFIG_ICD_IDLE_MODE_DURATION_SEC),
                  "mShortIdleModeDuration must be lesser or equal than mIdleModeDuration.");

    static_assert(System::Clock::Milliseconds32(CHIP_CONFIG_ICD_ACTIVE_MODE_DURATION_MS) <=
                      System::Clock::Seconds32(CHIP_CONFIG_ICD_IDLE_MODE_DURATION_SEC),
                  "Spec requires the IdleModeDuration be equal or greater to the ActiveModeDuration.");
    System::Clock::Milliseconds32 mActiveModeDuration = System::Clock::Milliseconds32(CHIP_CONFIG_ICD_ACTIVE_MODE_DURATION_MS);

    System::Clock::Milliseconds16 mActiveThreshold = System::Clock::Milliseconds16(CHIP_CONFIG_ICD_ACTIVE_MODE_THRESHOLD_MS);

    Protocols::SecureChannel::CheckInCounter mICDCounter;

    static_assert((CHIP_CONFIG_ICD_CLIENTS_SUPPORTED_PER_FABRIC) >= 1,
                  "Spec requires the minimum of supported clients per fabric be equal or greater to 1.");
    uint16_t mFabricClientsSupported = CHIP_CONFIG_ICD_CLIENTS_SUPPORTED_PER_FABRIC;

    static_assert((CHIP_CONFIG_ICD_MAXIMUM_CHECK_IN_BACKOFF_SEC) <= kMaxIdleModeDuration.count(),
                  "Spec requires the MaximumCheckInBackOff to be equal or inferior to 64800s");
    static_assert((CHIP_CONFIG_ICD_IDLE_MODE_DURATION_SEC) <= (CHIP_CONFIG_ICD_MAXIMUM_CHECK_IN_BACKOFF_SEC),
                  "Spec requires the MaximumCheckInBackOff to be equal or superior to the IdleModeDuration");
    System::Clock::Seconds32 mMaximumCheckInBackOff = System::Clock::Seconds32(CHIP_CONFIG_ICD_MAXIMUM_CHECK_IN_BACKOFF_SEC);

    // SIT ICDs SHALL have a SlowPollingThreshold shorter than or equal to 15s (spec 9.16.1.5)
    static constexpr System::Clock::Milliseconds32 kSitIcdSlowPollMaximum = System::Clock::Milliseconds32(15000);
    static_assert((CHIP_DEVICE_CONFIG_ICD_SIT_SLOW_POLL_LIMIT).count() <= kSitIcdSlowPollMaximum.count(),
                  "Spec requires the maximum slow poll interval for the SIT device to be smaller or equal than 15 s.");
    static constexpr System::Clock::Milliseconds32 kSITPollingThreshold = CHIP_DEVICE_CONFIG_ICD_SIT_SLOW_POLL_LIMIT;

#if CHIP_CONFIG_ENABLE_ICD_LIT == 0
    static_assert((CHIP_DEVICE_CONFIG_ICD_SLOW_POLL_INTERVAL <= kSitIcdSlowPollMaximum),
                  "LIT support is required for slow polling intervals superior to 15 seconds");
#endif
    // The Polling interval used in Idle mode
    System::Clock::Milliseconds32 mLITPollingInterval = CHIP_DEVICE_CONFIG_ICD_SLOW_POLL_INTERVAL;
    // The Polling interval used in Active mode
    System::Clock::Milliseconds32 mFastPollingInterval = CHIP_DEVICE_CONFIG_ICD_FAST_POLL_INTERVAL;

    static_assert((CHIP_DEVICE_CONFIG_ICD_SIT_POLLING_INTERVAL <= kSitIcdSlowPollMaximum),
                  "The SIT polling intervals must not exceed 15 seconds");
    // The Polling interval used in Idle mode when a LIT capable device operates in SIT mode and that is mLITPollingInterval is
    // greater than mSITPollingInterval
    System::Clock::Milliseconds32 mSITPollingInterval = CHIP_DEVICE_CONFIG_ICD_SIT_POLLING_INTERVAL;

    BitFlags<app::Clusters::IcdManagement::Feature> mFeatureMap;

    ICDMode mICDMode = ICDMode::SIT;
};

} // namespace chip
